JavaScript编程
对象
类型转换
数字到字符串
toString(radix):
进制转换,十进制转其他进制。toFixed(n):
小数点后指定位数。toExponential(n):
使用指数计数法,小数点前只有一位,指定小数点后的位数。toPrecision(n):
指定有效位数,如果有效数字的位数少于整数部分,会转换成指数计数。
(以上方法都会进行四舍五入或填充0)
字符串到数字
- Number(n):转换成整数或浮点数直接量。不能出现非法的尾随字符。
- parseInt(number,radix):只解析整数,提供第二参数,则以这个指定的进制转换基数。其他进制转换成十进制。会跳过空格,忽略后面的非数字内容。
- parseFloat(number):解析浮点数和整数,没有进制参数。
对象转换为原始值
- 对象转换成布尔值:所有对象(不包含null)都是true。
- 对象转换成字符串:首先toString(),没有则使用valueOf(),否则错误。
- 对象转换成数字:先valueOf(),没有则使用toString(),否则错误。
属性查询和设置
使用.或方括号来获取属性值。.右侧是一个简单标识符,[]内必须是一个计算结果为字符串的表达式。
以方括号的方式查询属性看起来更像数组,只是使用字符串索引而不是数字索引,这种数组就是关联数组,也叫散列,映射,字典。
查询一个不存在的属性会返回undefined。
删除属性
delete expression
:expression是属性访问表达式。
- 不能删除那些可配置性为false的属性。
- 不能删除通过变量声明和函数声明创建的全局对象的属性。
检测属性
4种方法检测属性:
- in运算符:继承属性或自有属性都返回true
- hasOwnPropery(str)方法:自有属性才返回true
- propertyIsEnumerable(str)方法:自有属性并且可枚举返回true
- 属性查询:通过.或方括号查询属性,判断是否全等undefined
枚举属性
3中方法枚举属性
- for/in循环:for(key in obj) {obj[key]}返回的是所有能够通过对象访问的、可枚举的(enumerated)属性,其中
既包括存在于实例中的属性,也包括存在于原型中的属性. - Object.keys(obj):返回对象中可枚举并且是自有属性的数组
- Object.getOwnPropertyNames(obj):返回对象中所有的自有属性,而不仅仅是可枚举属性
属性的特性
getter和setter
getter和setter定义的属性称为“存取器属性”,同时具有setter方法和getter方法,则具有读/写属性,否则只具备其中之一。
getter:当尝试查询get属性时,将调用get函数。
setter:当尝试设置set属性时,将调用set函数。
存取器属性
- get,set,configurable,enumerable
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18var book = {
_year: 2004,
edition: 1
};
// 注意这里是year而不是_year
Object.defineProperty(book, "year", {
get: function () {
return this._year;
},
set: function (newValue) {
if (newValue > 2004) {
this._year = newValue;
this.edition += newValue - 2004;
}
}
});
book.year = 2005;
alert(book.edition); //2
数据属性
[[value]], [[writable]], [[configurable]], [[enumerable]]
默认情况,直接创建的对象,除了value是undefined,全都是true
使用Object.defineProperty方法创建的属性都是false,undefined读取特性
Object.getOwnPropertyDescriptor(obj, prop)
- Object.getOwnPropertyDescriptors(obj)
设置特性
- Object.defineProperty(obj, prop, {attrib})
- Object.defineProperties(obj, {prop: {attrib},})
对象属性
原型属性
- Object.getPrototypeOf(obj):返回obj的原型对象
- isPrototype(obj): 检测一个对象是否是另一个对象的原型(类似instanceof元素符运算符不用驼峰写法,而方法则需要)
- o.constructor返回创建实例对象的Object的构造函数
- 使用instanceof来进行对象类型检测
类属性
表示对象的类型信息。略。
可扩展性
表示是否可以给对象添加新属性。默认都是显式可扩展的。
- Object.isExtensible():判断该对象是否可扩展。
- Object.preventExtensions():设置对象不可扩展。不可逆转。
- Object.seal():类似preventExtensions(),还将设置所有的自由属性为不可配置,封闭。不可逆转。
- Object.freeze():类似seal(),还将设置所有的数据属性为只读,冻结。
- isSeal(),isFrozen():检测是否封闭,冻结。
序列化对象
对象序列化(serialization)指将对象的状态转换为字符串,也可以字符串还原为对象。
Function.prototype.apply(thisArg, argsArray)
apply()方法调用一个函数,其具有一个指定的this值和一个数组(或者类数组对象)作为的参数。
apply 与 call() 非常相似,不同之处在于提供参数的方式。apply 使用参数数组而不是一组参数列表。
返回值是你调用的方法的返回值,若该方法没有返回值,则返回undefined。
Function.prototype.call(thisArg, arg1, arg2…)
call()方法调用一个函数,其具有一个指定的this值和分别提供的参数(参数列表)。
返回值是你调用的方法的返回值,若该方法没有返回值,则返回undefined。
Function.prototype.bind()
bind()方法创建一个新的函数,这个新函数被调用时,新函数的this设置为提供的值,新函数的参数列表前几项设置为提供的参数列表。
返回由指定的this值和初始化参数改造的原函数拷贝。
**区别apply,call,返回原函数拷贝而不是原函数的返回值。
arguments.callee
表示包含当前正在执行的函数
闭包
简单讲,闭包就是指有权访问另一个函数作用域中的变量的函数。
当闭包被返回或者被调用的时候,一定要注意this的值,这个值一般都是指向window的。
面向对象编程
对象以及原型
通过类可以创建任意多个具有相同属性和方法的对象,ECMAScript 中没有类的概念,因此它的对象也与基于类的语言中的对象有所不同。
ECMA-262 把对象定义为:“无序属性的集合,其属性可以包含基本值、对象或者函数。”
每个对象都是基于一个引用类型创建的,这个引用类型可以是原生的,比如Array,Date等,也可以是开发人员自定义的类型。
我们创建的每个函数都有一个prototype原型属性,这个属性是一个指针,指向一个对象。prototype就是通过调用构造函数而创建的对象实例的原型对象。包含特定类型的实例共享的属性和方法。
无论什么时候,只要创建了一个新函数,就会根据一组特定的规则为该函数创建一个prototype属性,这个属性指向函数的原型对象。
原型对象获得一个constructor指向构造函数。
当调用构造函数创建一个实例后,该实例的内部有一个指针,指向构造函数的原型对象。
每当代码读取某个对象的某个属性,都会执行一次搜索,先从对象实例开始,再到原型对象。
当为对象实例添加一个属性时,这个属性就会屏蔽原型对象中保存的同名属性。(基本值而言,引用类型则会改变)
继承
由于函数没有签名,在ECMAScript 中无法实现接口继承。ECMAScript 只支持实现继承,而且其实现继承主要是依靠原型链来实现的。
构造函数、原型和实例的关系:每个构造函数都有一个原型对象,原型对象都包含一个指向构造函数的指针,而实例都包含一个指向原型对象的内部指针。
我们让原型对象等于另一个类型的实例,此时的原型对象将包含一个指向另一个原型的指针。这就构成了实例和原型的链条,就是所谓的原型链。利用原型链的这个特性,将它作为实现继承的主要方法。
原型链:1)搜索实例;2)搜索SubType.prototype;3)搜索SuperType.prototype,最后一步才会找到该方法。在找不到属性或方法的情况下,搜索过程总是要一环一环地前行到原型链末端才会停下来。
所有函数的默认原型都是Object 的实例,因此默认原型都会包含一个内部指针,指向Object.prototype。这也正是所有自定义类型都会继承toString()、valueOf()等默认方法的根本原因
确定原型和实例的关系
第一种方式是使用instanceof 操作符,只要用这个操作符来测试实例与原型链中出现过的构造函数,结果就会返回true。
第二种方式是使用isPrototypeOf()方法。同样,只要是原型链中出现过的原型,都可以说是该原型链所派生的实例的原型,因此isPrototypeOf()方法也会返回true。给原型添加方法的代码一定要放在替换原型的语句之后
在通过原型链实现继承时,不能使用对象字面量创建原型方法。因为这样做就会重写原型链。
基本继承
基本思想:利用原型链的特性,将一个原型对象等于另一个类型的实例。
基本继承(原型链继承)的问题:最主要的问题来自包含引用类型值的原型原先的实例属性也就顺理成章地变成了现在的原型属性了。
原型链的第二个问题:在创建子类型的实例时,不能向超类型的构造函数中传递参数。
- 借用构造函数(经典继承或伪造对象)
基本思想:在子类型构造函数的内部调用超类型构造函数。
通过使用call方法改变this指向,就能借用了构造函数。如此就能解决传递参数的问题以及实例引用类型值的问题。
但借用构造函数没能解决方法都在构造函数中定义的问题。
- 组合继承(伪经典继承)
基本思想:使用原型链实现对原型属性和方法的继承,而通过借用构造函数来实现对实例属性的继承。如此就能通过在原型上定义方法实现函数复用,也能保证每个实例都有自己的属性。
融合了原型链和借用构造函数的优点,是JS中最常用的继承模式。
类似自定义对象中的组合使用原型模式和构造函数模式
Array API
- 创建数组的方式:Array构造函数、数组字面量
1
2
3
4
5
6// constructor
var color = new Array(length); // legnth表示长度,可给定一个数字
var color = new Array("red", "blue"); //这样可以直接创建数组元素
// []
var color = ["red", "blue"];
2.数组的项数保存在其length 属性中,这个属性始终会返回0 或更大的值。数组的length 属性很有特点——它不是只读的。通过设置这个属性,可以从数组的末尾移除项或向数组中添加新项。1
2
3
4
5
6var color = ["red", "blue"];
color.length = 1;
color[1]; // undefined
color[color.length] = "green";
color[color.length-1]; //green
检测数组
Array.isArray(array);
转换方法
数组转字符串:toString()
,返回由每个值的字符串拼接成以逗号分隔的字符串。
数组转字符串:join(分隔符)
,可以使用不同的分隔符来构建这个字符串。
注意:如果数组中的某一项的值是null 或者undefined,那么该值在join()、toLocaleString()、toString()和valueOf()方法返回的结果中以空字符串表示。
栈方法
栈是一种LIFO(Last-In-First-Out,后进先出)的数据结构,也就是最新添加的项最早被移除。
插入:push(str1,str2,),插入若干项,返回修改后数组的长度。
移除:pop(),移除最后一项,返回移除项
队列方法
队列数据结构的访问规则是FIFO(First-In-First-Out,先进先出)。
插入:push
移除:shift(),移除数组中第一项并返回该项,同时将数组长度减1。
重排序方法
反序排列:reverse();返回排序之后的数组
灵活排序:sort(fn(a, b)),默认按什序排列数组,将元素转为字符串再进行比较,即使是数值也比较的是字符串。为了更灵活的比较,可以使用比较函数,比较函数接收两个参数,比较函数通过大于0,小于0,等于0来排序。返回的是排序之后的数组。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15// 什序 asc
let compare = (a, b) => {
if(a < b) {
return -1;
} else if(a > b) {
return 1;
} else {
return 0;
}
}
// 降序 desc ,只要把前面两个return调换即可
// 对于会返回数字的比较函数,记忆:a-b就是a~b,什序,b-a就是b~a,降序
function compare(a, b) {
return a - b;
}
操作方法
数组副本连接:concat([ele1,[arr1),不传递参数,只是一个副本,传递参数,在副本末尾添加元素。
数组副本截断:slice(star, end),返回从开始到结束位置之间的项
数组删除、插入、替换:始终返回的是被删除项的数组。
删除:splice(开始的位置, 删除的项数)
插入:splice(开始的位置, 0, 元素1,元素2),跟删除一样,只是删除的项数是0,不删除直接添加。
替换:splice(开始的位置,删除项数,元素1,元素2),跟删除一样,只是删除后再添加。
位置方法
查找索引: indexOf(要找的元素,[起点位置),返回查找项在数组中的位置,找不到则返回-1。
lastIndexOf,从数组末尾开始向前找。
迭代方法
5个迭代方法:都接收两个参数,(fn(当前值item,当前索引index,数组本身array), [函数的作用域对象)。
every,filter,forEach,map,some
every():对数组中的每一项运行给定函数,如果该函数对每一项都返回true,则返回true。1
2
3
4
5var numbers = [1,2,3,4,5,4,3,2,1];
var filterResult = numbers.every(function(item, index, array){
return (item > 2);
});
alert(filterResult); // false
filter():对数组中的每一项运行给定函数,返回该函数会返回true 的项组成的数组。1
2
3
4
5var numbers = [1,2,3,4,5,4,3,2,1];
var filterResult = numbers.filter(function(item, index, array){
return (item > 2);
});
alert(filterResult); //[3,4,5,4,3]
forEach():对数组中的每一项运行给定函数,没有返回值
map():对数组中的每一项运行给定函数,返回每次函数调用的结果组成的数组。1
2
3
4
5var numbers = [1,2,3,4,5,4,3,2,1];
var mapResult = numbers.map(function(item, index, array){
return item * 2;
});
alert(mapResult); //[2,4,6,8,10,8,6,4,2]
some():对数组中的每一项运行给定函数,如果该函数对任一项返回true,则返回true。1
2
3
4
5var numbers = [1,2,3,4,5,4,3,2,1];
var filterResult = numbers.some(function(item, index, array){
return (item > 2);
});
alert(filterResult); // true
归并方法
reduce(fn(前一个值,当前值,当前索引,数组本身), )
:迭代数组的所有项,然后构建一个最终返回的值。1
2
3
4
5var values = [1, 2, 3, 4, 5];
var sum = values.reduce(function (prev, cur, index, array) {
return prev + cur;
});
alert(sum); //15
reduceRight()
,跟reduce
一样,只是从最后一项开始。
Date类型
Date类型使用自UTC(Coordinated Universal Time,国际协调时间)1970 年1 月1 日午夜(零时)开始经过的毫秒数来保存日期。
要创建一个日期对象,使用new 操作符和Date 构造函数即可。在调用Date 构造函数而不传递参数的情况下,新创建的对象自动获得当前日期和时间。如果想根据特定的日期和时间创建日期对象,必须传入表示该日期的毫秒数。
为了简化上述的毫秒,ES提供两个方法对日期进行计算,
Date.parse()
,Date.UTC()
.Date.parse()
,可用的时间格式:
“月/日/年”
“英文月 日,年”
“ISO 8601 YYYY-MM-DDTHH:mm:ss”
例如:var someDate = new Date(Date.parse("2018-11-19T15:12:55"))
var loveDay = new Date("11/18/2018");
直接将表示日期的字符串传递个构造函数也会默认调用parse方法Date.UTC(年,月,日,小时,分钟,秒)
,只有年月是必须的,其他如果省略则默认是0。尽管你给定了一个时间值,编译器会自动采用本地时区。所以少用吧。了解就好。1
2var love = new Date(Date.UTC(2018, 10, 18, 8, 0,0));
//Sun Nov 18 2018 16:00:00 GMT+0800 (中国标准时间)日期的get和set
1
2
3
4
5
6
7
8
9var love = new Date("2018-11-27T17:30:01");
// get 获取年月日不需要复数,时间需要复数
love.getFullYear(); // 2018 都是number类型
love.getMonth(); // 10 对月份是从0开始算起
love.getDate(); // 27 表示这个月的第几天
love.getHours(); // 17
love.getMinutes(); // 30
love.getSeconds(); // 1
// set 只是将前缀换成set,并赋值
RegExp类型
创建一个正则表达式
1
2// /pattern/flags
var expression = /love [ui]/g;元字符:在正则表达式中有特殊用途,如果要使用这些字符就要进行转义
( [ { \ ^ $ | ) ? * + .]}
RegExp实例属性
global
:boolean, if set flags of gignoreCase
: boolean, if set flags of ilastIndex
: number, the next items’ index of start,from zero to run.source
: the RegExp’s string.RegExp实例方法
exec(string)
,design for the capture group and the string is applying the pattern.It returns a Array and contains two attrib:index and input.index表示第一个匹配项出现在字符串的位置。input则表示string。当出现两个以上则没有这两个属性。
在数组中,第一项是与整个模式匹配的字符串,其他项则是捕获组匹配到的字符串。如果没有捕获组,则数组只包含一项。在全局匹配模式下,lastIndex 的值在每次调用exec()后都会增加,而在非全局模式下则始终保持不变。也就是说设置了g,则每一次调用exec都会在字符串中继续查找新匹配项。1
2
3
4
5
6
7
8var text = "mom and dad and baby";
var pattern = /mom( and dad( and baby)?)?/gi;
var matches = pattern.exec(text);
alert(matches.index); // 0
alert(matches.input); // "mom and dad and baby"
alert(matches[0]); // "mom and dad and baby"
alert(matches[1]); // " and dad and baby"
aler t(matches[2]); // " and baby"
test(string)
,返回boolean,表示是否与模式与字符串匹配。经常用于if语句中,用于验证用户输入。
Function类型
函数名实际上是一个指向函数对象的指针,不会与某个函数绑定。这就是JS函数没有重载的原因。
声明函数的方式有三种:
通常用函数声明语法:function sum(a, b) {return a + b;}
也常用函数表达式定义函数:var sum = function(a, b) {return b}
虽然可以,但不要用Function构造函数var sum = new Function(a, b)
。这其实也说明了,函数就是对象。没有重载,因为是函数名指针,函数名相同,则表示同一个指针,后者覆盖前者。
函数声明和函数表达式区别:函数声明有一个函数声明提升的过程(类似变量提升),而函数表达式没有这个过程。这个重要的区别,让函数声明可以在调用后才进行初始化,而函数表达式不行。
函数内部属性:arguments和this。arguments就是参数的意思,是一个类数组对象。this引用的是函数执行的环境对象。
函数属性和方法
- 属性:length和prototype,length表示函数希望接收的参数个数,prototype上文解释。
- 方法:apply(参数数组)和call(参数列表),bind(obj),返回一个函数实例。参考上文解释。
基本包装类型
3个特殊的引用类型:Boolean、Number 和String。
每当读取一个基本类型值的时候,后台就会创建一个对应的基本包装类型的对象,从而让我们能够调用一些方法来操作这些数据。
原理:为了让我们实现这种直观的操作,后台已经自动完成了一系列的处理
1
2
3var s1 = new String("some text");
var s2 = s1.substring(2);
s1 = null;
String类型
Number类型上文有提到,Boolean很简单不必写。重点在String。因为我们常常需要对字符串进行操作。
charAt(n)
n位置的字符(简单方式:像数组一样使用[]访问),charCodeAt(n)
n位置的字符的编码。字符串操作方法
concat(str1, str2...)
拼接多个字符串,返回新的字符串。(应该更多的使用+)slice(start,end)
截断字符串,返回新的子字符串。substr(start, length)
,同样是截断字符串,跟slice的区别在于,第二个参数是指长度,返回新的子字符串。字符串位置方法
indexOf(str),lastIndexOf(str)
,返回子字符串的索引。删除空格
trim(),trimLeft(),trimRight()。
返回去掉空格后的新字符串。大小写转化
toLowerCase(), toUppercase()
字符串的模式匹配
match(pattern)
,在字符串上调用这个方法与在正则表达式中调用exec方法基本是一样的。返回的是一个数组。区别在于,exec每次只是返回一项,而match返回所有项。search(pattern)
,返回第一个匹配项的索引。替换字符串
replace(pattern|oldString, newString|fn)
如果第一个参数是一个字符串,则只会替换第一个字符串。提供正则+g则可以替换所有子字符串。分隔字符串
split(str|pattern)
,基于指定的分隔符将一个字符串分割成子字符串,返回一个数组。跟join相反。编译字符编码:
String.fromCharCode(code)
本地字符串大小比较:
localeCompare(str)
; 字符串排在参数前则-1;等于则0;排在参数后则1;
单体内置对象
- 由ECMA实现的,不依赖宿主环境的对象那个,在程序执行之前就存在了。
Global对象
所有在全局作用域中定义的属性和函数,都是Global对象的属性。很多可以直接使用的函数实际上就是Global对象的方法,比如:isNaN(),parseInt()。
URI编码方法:
encodeURI() encodeURIComponent()
区别在于,前者不会对本身属于URI的特殊字符进行编码,比如冒号和斜杠。而后者会对它发现的任何标准字符进行编码。
var uri = “http://www.wrox.com/illegal value.htm#start”;
//“http://www.wrox.com/illegal%20value.htm#start“
alert(encodeURI(uri));
//“http%3A%2F%2Fwww.wrox.com%2Fillegal%20value.htm%23start”
alert(encodeURIComponent(uri));URI解码方法:
decodeURI() decodeURIComponent()
分别对应解码上述两者的编码。eval(执行的代码的字符串),就相当于一个解释器,解析后执行。但很危险,有被代码注入的危险。
在浏览器中,实际上window就是全局对象。
Math对象
最大最小值,
max() min()
,寻找数组的最大最小值Math.max.apply(Math, [1, 2, 5, 8])
舍入方法,
ceil() floor() round()
向上取整,向下取整 四舍五入随机数,random()返回大于等于0小于1的随机数。
获取某个范围内的整数。Math.floor(random()*可能值的总数 + 第一个可能的值)其他数学操作:
abs(num) pow(num,power) sqrt(num)
函数表达式
闭包的概念:有权访问另一个函数作用域中的变量的函数。闭包即函数。不要把这个概念复杂化了,闭包就是一个可访问另一个函数中的变量的函数。
闭包和变量:闭包所保存的是整个变量对象,而不是特殊的变量。想象中,应该是每个函数都返回不同的i,但实际上都返回10.因为每个函数的作用域链中保存着相同的createFunctions()函数的活动对象,所以它们引用的是同一个变量i。当createFunction函数返回后,i的值是10.此时每个函数都引用着保存变量i的同一个变量对象,所以每个函数都返回10.
1
2
3
4
5
6
7
8
9function createFunctions(){
var result = new Array();
for (var i=0; i < 10; i++){
result[i] = function(){
return i;
};
}
return result;
}
解决方案:没有直接把闭包赋值给数组,而是定义了一个匿名函数,并将立即执行该匿名函数的结果赋给数组由于函数参数是按值传递的,所以就会将变量i 的当前值复制给参数num.1
2
3
4
5
6
7
8
9
10
11function createFunctions(){
var result = new Array();
for (var i=0; i < 10; i++){
result[i] = function(num){
return function(){
return num;
};
}(i);
}
return result;
}
关于this对象
1
2
3
4
5
6
7
8
9
10var name = "The Window";
var object = {
name: "My Object",
getNameFunc: function () {
return function () {
return this.name;
};
}
};
alert(object.getNameFunc()()); //"The Window"(在非严格模式下)模仿块级作用域
在ES6之前没有块级作用域的概念。在块语句中定义的变量,实际上是在包含函数中而非语句中创建的。1
2
3
4
5
6
7function outputNumbers(count) {
for (var i = 0; i < count; i++) {
alert(i);
}
// 如果for是一个块级作用域,下面的i是会错误的,但JS中是没问题的
alert(i);
}
JavaScript不会告诉你是否多次声明了同一个变量,它只会对后续的声明视而不见。用匿名函数可以模拟块级作用域(私有作用域)避免这个问题。1
2
3
4// 块级作用域(私有作用域)语法
(function() {
})();
必须将函数声明包含在一对括号中,表示它是一个函数表达式。否则出错1
2
3
4
5// 出错:因为JS将function关键字当做一个函数声明的开始,但函数声明后面
// 不能接圆括号,而函数表达式可以跟圆括号,表示调用函数。
function() {
}()
无论在什么地方,只要临时需要一些变量,就可以使用私有作用域。1
2
3
4
5
6
7
8
9
10
11function outputNumbers(count) {
// 在私有作用域定义的任何变量,执行结束后都被销毁
(function() {
for (var i = 0; i < count; i++) {
alert(i);
}
})();
// 因为我们在for外部加了一个私有作用域,这里会出错
alert(i);
}
这种技术经常在全局作用于汇总被用在函数外部,从而限制向全局作用域中添加过多的变量和函数。因为过多的全局变量和函数很容易导致命名冲突,而创建私有作用域则没有这个问题
- 私有变量
在JS中没有私有成员概念,所有对象属性都是公有的。但也有一个私有变量的概念。在函数中定义的变量都可以认为是私有变量,因为不能再函数外部访问这些变量私有变量包括函数的参数、局部变量和函数颞部定义的其他函数
执行环境和作用域
execution context,执行环境。定义了变量或函数有权访问的其他数据,决定了各自的行为。每一个执行环境都有一个与之关联的变量对象(variable object),环境汇总定义的所有变量和函数都保存在这个对象中。
某个执行环境的diamante执行完毕后,保存在其中的变量的变量对象就被销毁。
当执行流进入一个函数时,函数的环境就会被推入一个环境中,执行后,将环境弹出,把控制权返回给之前的执行环境。
当代码在一个环境中执行时,会创建变量对象的**作用域链(scope chain)。作用域链用于保证对执行环境中能访问的所有变量和函数的有序访问。
作用域链的前端,始终都是当前执行的代码所在环境的变量对象。如果这个环境是函数,则将其活动对象(activation object)作为变量对象。活动对象在最开始时只包含一个变量,即arguments 对象(这个对象在全局环境中是不存在的)。作用域链中的下一个变量对象来自包含(外部)环境,而再下一个变量对象则来自下一个包含环境。这样,一直延续到全局执行环境;全局执行环境的变量对象始终都是作用域链中的最后一个对象。
标识符解析是沿着作用域链一级一级地搜索标识符的过程。搜索过程始终从作用域链的前端开始,然后逐级地向后回溯,直至找到标识符为止(如果找不到标识符,通常会导致错误发生)。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16var color = "blue";
function changeColor() {
var anotherColor = "red";
function swapColors() {
var tempColor = anotherColor;
anotherColor = color;
color = tempColor;
// 这里可以访问color、anotherColor 和tempColor
}
// 这里可以访问color 和anotherColor,但不能访问tempColor
swapColors();
}
// 这里只能访问color
changeColor();